package scales.xml.xpath
import scales.utils._
import scala.collection.generic.CanBuildFrom
import scales.utils.one
import scales.xml.{Elem, Attributes, Attribute, XCC, XmlItem, XmlPath, AttributeQName, ScalesXml, text, UnprefixedQName}
import scales.xml.impl.EqualsHelpers
case class AttributePath(attribute: Attribute, parent: XmlPath)
case class XmlPathComparisoms(path: XmlPath) {
import ScalesXml.xmlpathText
def ===(other: String) = text(path) == other
}
case class AttributePathComparisoms(path: AttributePath) {
import ScalesXml.attribPathText
def ===(other: String) = text(path) == other
}
case class AttributePaths[PT <: Iterable[XmlPath]](attributes: Iterable[AttributePath], path: XPathInfo, cbf: CanBuildFrom[PT, XmlPath, PT]) {
import scales.xml.impl.EqualsHelpers.toQName
def \^(): XPath[PT] = new XPath[PT](path.copy(nodes = List(attributes.map(_.parent))), cbf)
def *@(pred : AttributePath => Boolean) : AttributePaths[PT] =
AttributePaths(attributes.filter { pred(_) }, path, cbf)
def *@(attrQName : AttributeQName) : AttributePaths[PT] =
*@({ attributePath : AttributePath => toQName(attributePath.attribute.name) =:= toQName(attrQName) })
def *@(qname : UnprefixedQName) : AttributePaths[PT] =
*@({ attributePath : AttributePath => toQName(attributePath.attribute.name) =:= qname })
def one: Iterable[AttributePath] = if (attributes.size != 1) Nil else scales.utils.one(attributes.head)
def oneOr(handler: Iterable[AttributePath] => Iterable[AttributePath]) = if (attributes.size == 1) scales.utils.one(attributes.head) else handler(attributes)
def ===( textValue : => String ) =
*@( a => a.attribute.value == textValue )
}
case class XPathInfo(nodes: Iterable[Iterable[XmlPath]], mustBeSorted: Boolean = false, filterDuplicates: Boolean = false, initialNode: Boolean = false, eager : Boolean = false, direct : Boolean = false)
class XPath[PT <: Iterable[XmlPath]](val path: XPathInfo, val cbf: CanBuildFrom[PT, XmlPath, PT]) extends ElementStep with OtherNodeTypes with AttributeAxis with SiblingsAxis with DocumentSplitters {
type T = PT
def newThis(xpathInfo: XPathInfo): XPath[T] =
new XPath[T](xpathInfo, cbf)
def empty: Iterable[XmlPath] = cbf().result
def just(only: XmlPath): Iterable[XmlPath] = (cbf() += only).result
def in(f: (XPath[T]) => XPath[T]): XPath[T] =
f(this)
def |>(f: (XPath[T]) => XPath[T]) = in(f)
def one: Iterable[XmlPath] = {
val nodes = ScalesXml.fromXPathToIterable(this)
if (nodes.size != 1) Nil
else scales.utils.one(nodes.head)
}
def oneOr(handler: XPath[T] => Iterable[XmlPath]) = {
val nodes = ScalesXml.fromXPathToIterable(this)
if (nodes.size == 1) scales.utils.one(nodes.head)
else handler(this)
}
def |( other : XPath[T] ) : XPath[T] =
new XPath(XPathInfo(
nodes = path.nodes ++ other.path.nodes,
mustBeSorted = true, filterDuplicates = true, initialNode = false, eager = path.eager | other.path.eager), cbf)
}
class DirectXPath[PT <: Iterable[XmlPath]](override val path: XPathInfo, override val cbf: CanBuildFrom[PT, XmlPath, PT]) extends XPath[PT](path, cbf) {
override def process(newNodes: Iterable[Iterable[XmlPath]], info : XPathInfo = path) = {
newThis(info.copy( nodes = newNodes ))
}
}
object Axis {
def eager_recUnpack(xmlPath: Iterable[XmlPath]): List[XmlPath] = {
var res : List[XmlPath] = Nil
val i = xmlPath.iterator
while(i.hasNext) {
val child = i.next
if (child.isItem)
res = child :: res
else {
res = (child :: res ).reverse_::: (
eager_recUnpack(child: Iterable[XmlPath]) )
}
}
res
}
def lazy_recUnpack(axis : Axis)(xmlPath: Iterable[XmlPath]): Iterable[XmlPath] =
xmlPath.flatMap { child =>
if (child.isItem) axis.just(child)
else {
axis.just(child) ++
lazy_recUnpack(axis)(child: Iterable[XmlPath])
}
}
}
trait Axis {
import Axis._
type T <: Iterable[XmlPath]
val cbf: CanBuildFrom[T, XmlPath, T]
val path: XPathInfo
def newThis(xpathInfo: XPathInfo): XPath[T]
def process(newNodes: Iterable[Iterable[XmlPath]], info : XPathInfo = path) = {
val nn =
if (path.eager) {
if (newNodes.exists(_.isEmpty))
newNodes.filter(!_.isEmpty)
else
newNodes
} else newNodes.filter(!_.isEmpty)
newThis(info.copy( nodes = nn))
}
def empty: Iterable[XmlPath]
def just(only: XmlPath): Iterable[XmlPath]
final def xflatMap(f : Iterable[XmlPath] => Iterable[Iterable[XmlPath]] ) =
process( path.nodes.flatMap( f ) )
final def xfilter( f : Iterable[XmlPath] => Boolean ) =
process(path.nodes.filter( f ) )
protected final def xlast( onN : Int => Boolean ) : XPath[T] =
xfilter{ x => onN( x.size) }
def last_>(n : => Int): XPath[T] =
xlast( _ > n )
def last_<(n : => Int): XPath[T] =
xlast( _ < n )
def last_==(n : => Int): XPath[T] =
xlast( _ == n )
def pos_eq_last : XPath[T] =
xmap { _.lastOption }
def pos_>(pos : => Int): XPath[T] =
xflatMap { path =>
val n = path.drop(pos)
if (n.isEmpty)
empty
else
one(n)
}
def pos_<(pos : => Int): XPath[T] =
xflatMap { path =>
val n = path.take(pos-1)
if (n.isEmpty)
empty
else
one(n)
}
def pos_==(p: => Int) = pos(p)
def pos(pos: => Int): XPath[T] =
xflatMap { path =>
val n = path.take(pos)
if (n.size != pos)
empty
else
one(just(n.last))
}
def \^ : XPath[T] =
process(path.nodes.flatMap { path =>
path.flatMap { x =>
if (x.top.isLeft) empty
else one(just(x.top.right.get))
}
}, path.copy(initialNode = false, direct = false))
def \ : XPath[T] =
newThis(path.copy(initialNode = false, direct = false))
def \+ : XPath[T] =
process(path.nodes.flatMap {
_.flatMap { child =>
if (child.isItem) just(child)
else one(child)
}
}, path.copy(initialNode = false, direct = false))
def \\ : XPath[T] = {
val f =
if (path.eager) eager_recUnpack _
else lazy_recUnpack(this) _
process(
(if (path.initialNode) path.nodes
else List())
++ path.nodes.flatMap { f },
path.copy(initialNode = false, direct = false))
}
final def xmap( f : Iterable[XmlPath] => Iterable[XmlPath] ) =
process(path.nodes.map{ f })
final def filter(pred: XmlPath => Boolean): XPath[T] =
xmap{ _.filter(pred) }
final def ===( textValue : => String ) =
filter(x => XmlPathText.text(x) == textValue)
}